#include <net-snmp/net-snmp-config.h>
#include <net-snmp/net-snmp-includes.h>

#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <stdlib.h>
#include <lua5.3/lua.h>
#include <lua5.3/lauxlib.h>
#include <lua5.3/lualib.h>
#include "kvec.h"

#ifdef HAVE_WINSOCK_H
#include <winsock.h>
#endif

/*数据结构*/
typedef struct host
{
    /* data */
    const char *name;
    const char *community;
    const char *ip;
} host;

kvec_t(host) hosts;

typedef struct oid
{
    const char *Name;
    oid Oid[MAX_OID_LEN];
    int OidLen;
} oidSelf;

/*
 * poll all hosts in parallel
 */
typedef struct session
{
    struct snmp_session *sess; /* SNMP session data */
    oidSelf *current_oid;      /* How far in our poll are we */
    oidSelf *oldpt;
} session;

int active_hosts = 0; /* hosts that we have not completed */
//end

void error(lua_State *L, const char *fmt, ...)
{
    va_list argp;
    va_start(argp, fmt);
    vfprintf(stderr, fmt, argp);
    va_end(argp);
    lua_close(L);
    exit(EXIT_FAILURE);
}

void ParseLuaTable(lua_State *L)
{
    if (!lua_istable(L, -1))
    {
        return;
    }

    host selfHost;
    int needPush = 0;

    lua_pushnil(L);
    while (lua_next(L, -2))
    {
        //fprintf(stdout, "%s : %s    ", luaL_typename(L, -2), luaL_typename(L, -1));
        int nKeyType = lua_type(L, -2);
        int nValueType = lua_type(L, -1);
        const char *key;

        if (nKeyType == LUA_TSTRING)
        {
            //fprintf(stdout, "\"%s\",", lua_tostring(L, -2));
            key = lua_tostring(L, -2);
        }
        //fprintf(stdout, "   ");
        if (nValueType == LUA_TSTRING)
        {
            //fprintf(stdout, "\"%s\"", lua_tostring(L, -1));
            if (0 == strcmp(key, "host"))
            {
                selfHost.name = lua_tostring(L, -1);
            }
            else if (0 == strcmp(key, "community"))
            {
                selfHost.community = lua_tostring(L, -1);
            }
            else if (0 == strcmp(key, "ip"))
            {
                selfHost.ip = lua_tostring(L, -1);
            }
            needPush++;
        }
        else if (nValueType == LUA_TTABLE)
        {
            //fprintf(stdout, "\n");
            ParseLuaTable(L);
        }

        lua_pop(L, 1);
        //fprintf(stdout, "\n");
    }
    if (needPush)
    {
        needPush = 0;
        kv_push(host, hosts, selfHost);
    }
}

void ParseLuaTableOfOid(lua_State *L, oidSelf *oids)
{
    if (!lua_istable(L, -1))
    {
        return;
    }

    lua_pushnil(L);
    while (lua_next(L, -2))
    {
        //fprintf(stdout, "%s : %s    ", luaL_typename(L, -2), luaL_typename(L, -1));
        int nKeyType = lua_type(L, -2);
        int nValueType = lua_type(L, -1);
        const char *key;

        if (nKeyType == LUA_TNUMBER)
        {
            //fprintf(stdout, "%g,", lua_tonumber(L, -2));
        }
        else if (nKeyType == LUA_TSTRING)
        {
            //fprintf(stdout, "\"%s\",", lua_tostring(L, -2));
        }
        //fprintf(stdout, "   ");
        if (nValueType == LUA_TSTRING)
        {
            //fprintf(stdout, "\"%s\"", lua_tostring(L, -1));
            const char *oidstr = lua_tostring(L, -1);
            oids->Name = oidstr;
            oids++;
            oids->Name = NULL;
        }
        else if (nValueType == LUA_TTABLE)
        {
            //fprintf(stdout, "\n");
            ParseLuaTableOfOid(L, oids);
        }

        lua_pop(L, 1);
        //fprintf(stdout, "\n");
    }
}

/*snmp异步*/
void initialize(void)
{
    //struct oid *op = oids;

    /* Win32: init winsock */
    SOCK_STARTUP;

    /* initialize library */
    init_snmp("asynchapp");

    /* parse the oids */
    /*while (op->Name)
  {
    op->OidLen = sizeof(op->Oid) / sizeof(op->Oid[0]);
    if (!read_objid(op->Name, op->Oid, &op->OidLen))
    {
      snmp_perror("read_objid");
      exit(1);
    }
    op++;
  }*/
}

/*
 * simple printing of returned data
 */
int print_result(int status, struct snmp_session *sp, struct snmp_pdu *pdu)
{
    char buf[1024];
    struct variable_list *vp;
    int ix;
    struct timeval now;
    struct timezone tz;
    struct tm *tm;

    gettimeofday(&now, &tz);
    tm = localtime(&now.tv_sec);
    fprintf(stdout, "%.2d:%.2d:%.2d.%.6d ", tm->tm_hour, tm->tm_min, tm->tm_sec,
            now.tv_usec);
    switch (status)
    {
    case STAT_SUCCESS:
        vp = pdu->variables;
        if (pdu->errstat == SNMP_ERR_NOERROR)
        {
            while (vp)
            {
                snprint_variable(buf, sizeof(buf), vp->name, vp->name_length, vp);
                fprintf(stdout, "%s: %s\n", sp->peername, buf);
                vp = vp->next_variable;
            }
        }
        else
        {
            for (ix = 1; vp && ix != pdu->errindex; vp = vp->next_variable, ix++)
                ;
            if (vp)
                snprint_objid(buf, sizeof(buf), vp->name, vp->name_length);
            else
                strcpy(buf, "(none)");
            fprintf(stdout, "%s: %s: %s\n",
                    sp->peername, buf, snmp_errstring(pdu->errstat));
        }
        return 1;
    case STAT_TIMEOUT:
        fprintf(stdout, "%s: Timeout\n", sp->peername);
        return 0;
    case STAT_ERROR:
        snmp_perror(sp->peername);
        return 0;
    }
    return 0;
}

/*
 * response handler
 */
int asynch_response(int operation, struct snmp_session *sp, int reqid,
                    struct snmp_pdu *pdu, void *magic)
{
    session *host = (session *)magic;
    struct snmp_pdu *req;

    if (operation == NETSNMP_CALLBACK_OP_RECEIVED_MESSAGE)
    {
        if (print_result(STAT_SUCCESS, host->sess, pdu))
        {
            host->current_oid++; /* send next GET (if any) */
            if (host->current_oid->Name)
            {
                //fprintf(stdout,"\n测试：%s\n",host->current_oid->Name);
                req = snmp_pdu_create(SNMP_MSG_GET);
                snmp_add_null_var(req, host->current_oid->Oid, host->current_oid->OidLen);
                if (snmp_send(host->sess, req))
                    return 1;
                else
                {
                    snmp_perror("snmp_send");
                    snmp_free_pdu(req);
                }
            }

            free(host->oldpt);
            host->oldpt = NULL;
            host->current_oid = NULL;
        }
    }
    else
        print_result(STAT_TIMEOUT, host->sess, pdu);

    /* something went wrong (or end of variables) 
   * this host not active any more
   */
    active_hosts--;
    return 1;
}

void asynchronous(session sessions[], lua_State *L, int arrayLen)
{
    session *hs = sessions;
    host *hp;

    /* startup all hosts */

    for (int i = 0; i < arrayLen; hs++, i++)
    {
        struct snmp_pdu *req;
        struct snmp_session sess;
        host iHost = kv_a(host, hosts, i);
        snmp_sess_init(&sess); /* initialize session */
        sess.version = SNMP_VERSION_2c;
        sess.peername = strdup(iHost.ip);
        sess.community = strdup(iHost.community);
        sess.community_len = strlen(sess.community);
        sess.callback = asynch_response; /* default callback */
        sess.callback_magic = hs;
        if (!(hs->sess = snmp_open(&sess)))
        {
            snmp_perror("snmp_open");
            continue;
        }

        //修改oid结构的复制操作：
        lua_getglobal(L, iHost.name);

        int oidLen = luaL_len(L, -1);

        oidSelf *oids = (oidSelf *)malloc(sizeof(oidSelf) * oidLen);

        ParseLuaTableOfOid(L, oids);

        for (int i = 0; i < oidLen; i++, oids++)
        {
            if (oids->Name)
            {
                oids->OidLen = sizeof(oids->Oid) / sizeof(oids->Oid[0]);
                if (!read_objid(oids->Name, oids->Oid, &oids->OidLen))
                {
                    snmp_perror("read_objid");
                    exit(1);
                }
            }
            //fprintf(stdout, "%s\t,%ld\t,%d\n", oids[i].Name, oids[i].Oid, oids[i].OidLen);
        }
        hs->current_oid = oids - oidLen;
        hs->oldpt = oids - oidLen;

        req = snmp_pdu_create(SNMP_MSG_GET); /* send the first GET */
        snmp_add_null_var(req, hs->current_oid->Oid, hs->current_oid->OidLen);
        if (snmp_send(hs->sess, req))
            active_hosts++;
        else
        {
            snmp_perror("snmp_send");
            snmp_free_pdu(req);
        }
    }

    /* loop while any active hosts */

    while (active_hosts)
    {
        int fds = 0, block = 1;
        fd_set fdset;
        struct timeval timeout;

        FD_ZERO(&fdset);
        snmp_select_info(&fds, &fdset, &timeout, &block);
        fds = select(fds, &fdset, NULL, NULL, block ? NULL : &timeout);
        if (fds < 0)
        {
            perror("select failed");
            exit(1);
        }
        if (fds)
            snmp_read(&fdset);
        else
            snmp_timeout();
    }

    /* cleanup */

    for (int i = 0; i < arrayLen; i++, hs++)
    {
        //fprintf(stdout, "OUT1\n");
        if (hs->sess)
        {
            snmp_close(hs->sess);
        }
    }
}

//end

int main()
{
    kv_init(hosts);
    lua_State *L = luaL_newstate();
    luaL_openlibs(L);
    int error = 0;

    luaL_loadfile(L, "test.lua");
    error = lua_pcall(L, 0, 0, 0);
    if (error)
    {
        fprintf(stderr, "%s", lua_tostring(L, -1));
        lua_pop(L, 1); /* pop error message from the stack */
    }

    lua_getglobal(L, "data");
    int hostLen = luaL_len(L, -1);

    fprintf(stdout, "数量：%d\n", hostLen);
    ParseLuaTable(L);

    if (error)
    {
        fprintf(stderr, "%s", lua_tostring(L, -1));
        lua_pop(L, 1); /* pop error message from the stack */
    }

    int arrayLen = kv_size(hosts);
    session sessions[arrayLen];
    fprintf(stdout, "-------------------数组(%d)---------------\n", arrayLen);

    for (int i = 0; i < arrayLen; i++)
    {
        host iHost = kv_a(host, hosts, i);
        fprintf(stdout, "%d  %s  %s  %s\n", i, iHost.community, iHost.name, iHost.ip);
    }

    initialize();

    asynchronous(sessions, L, arrayLen);

    //fprintf(stdout, "OUT\n");

    kv_destroy(hosts);

    lua_close(L);

    return 0;
}